pyxis: allow unprivileged user namespaces so enroot works on Ubuntu 23.10+ - #1392
pyxis: allow unprivileged user namespaces so enroot works on Ubuntu 23.10+#1392100milliongold wants to merge 3 commits into
Conversation
…3.10+ Ubuntu 23.10 and later ship with kernel.apparmor_restrict_unprivileged_userns=1. Under that default, enroot-nsenter fails when pyxis starts a container: enroot-nsenter: failed to create user namespace: Permission denied The nvidia.enroot galaxy role does not handle this, and DeepOps lists Ubuntu 24.04 LTS as a supported OS, so container jobs are broken out of the box on a supported platform. Add a sysctl task on compute nodes, gated on the presence of the knob in /proc rather than on the distribution version, so it is a no-op on kernels built without AppArmor userns restrictions. Observed on DGX OS 7.5.0 (Ubuntu-based) with Slurm 26.05.1 and pyxis 0.11.1: srun --container-image=... failed with the message above until the sysctl was set. Signed-off-by: Jea-Eok-Kim <je.kim@xiilab.com>
dholt
left a comment
There was a problem hiding this comment.
Please replace the unconditional host-wide sysctl change with a narrowly scoped AppArmor policy for enroot-nsenter and provide evidence from a real Pyxis/Enroot container launch on an affected supported OS. If a global sysctl fallback is still needed for unusual installations, make it an explicit, documented, default-off administrator choice rather than changing the security default whenever the kernel knob exists.
Automated triage review (agent-generated on the maintainer's behalf; a human maintainer decides merges).
Clearing kernel.apparmor_restrict_unprivileged_userns lets every process on the host create unprivileged user namespaces, which is the attack surface the Ubuntu default was added to reduce. The role now installs an AppArmor profile that grants the userns capability to /usr/bin/enroot-nsenter alone and leaves the host default in place. enroot-nsenter is the only enroot executable that references CLONE_NEWUSER (checked against enroot 3.2.0-1), so the profile does not need to cover enroot-mount, enroot-switchroot, enroot-mksquashovlfs or enroot-aufs2ovlfs. The host-wide sysctl remains available for installations where the profile cannot be used, but it is now an explicit, documented, default-off choice: pyxis_userns_allow_globally, false by default. If the profile fails to load the play stops with the parser error and points at that variable, rather than silently falling back to the weaker setting. Both paths stay a no-op where /proc/sys/kernel/apparmor_restrict_unprivileged_userns does not exist, and the profile path additionally requires apparmor_parser. Evidence so far, on a DGX B300 running DGX OS 7.5 (Ubuntu 24.04.4, AppArmor 4.0 ABI available): $ apparmor_parser -Q -T -v enroot-nsenter Addition succeeded for "enroot-nsenter". $ grep -l CLONE_NEWUSER /usr/bin/enroot-* /usr/bin/enroot-nsenter What is still missing is a container launch on a host where the restriction is active. This node was already worked around out-of-band (/etc/sysctl.d/91-enroot.conf, not owned by any package, sets the knob to 0), so it is not in the affected state, and restoring the restriction to reproduce the failure would break the Pyxis jobs currently running on it. I will add the before/after launch output once the node is free; please hold the merge until then if that evidence is required.
|
Agreed on all three points — clearing What changed (commit
Why the profile only covers Checked against enroot 3.2.0-1, so Evidence so far, on a DGX B300 running DGX OS 7.5 (Ubuntu 24.04.4, AppArmor 4.0 ABI available): What is still missing, and I would rather say so than leave it implied: I do not have a container launch on a host where the restriction is active. This node was already worked around out-of-band before I picked up the issue — I will add the before/after launch output once the node is free. Please hold the merge until then if that evidence is required — I would rather this sit than be merged on a parser check alone. |
|
The node is free now, so here is the container launch evidence I said I would add. To produce it I had to put this host into the affected state first: it carries an out-of-band Environment Before — restriction active, no profile Install the profile After — same launch, profile loaded, restriction still active The knob reads 1 during both launches. That is the part I wanted on the record: the container starts with the OS security default left in place, which is what your review asked for and what the original patch did not do. Afterwards the profile was unloaded and removed, Two notes on the run itself. The image is plain |
dholt
left a comment
There was a problem hiding this comment.
The scoped AppArmor profile addresses the concern with the original global setting. A few convergence gaps remain in roles/pyxis/tasks/main.yml:
- Loading the profile only when its file changes means a failed first load, or an unloaded profile with an unchanged file, is not repaired on the next run. Reconcile the effective profile on reruns.
- With the restriction active and
apparmor_parserabsent, the default scoped path is silently skipped. Install the required userspace dependency or fail clearly. - Turning the global fallback back off leaves its managed sysctl setting behind. Handle or clearly document that migration without overwriting unrelated site policy.
Please cover these transitions with focused regression tests. This does not require weakening the host-wide policy.
…back Three convergence gaps from review. The profile was loaded only when its file changed, so a node whose first load failed, or whose profile was unloaded by hand while the file stayed as it was, stayed broken across reruns. The kernel's own profile list is now consulted and the load runs when either the file changed or the profile is not currently loaded. With the restriction active and apparmor_parser absent, every profile task was skipped and the play reported success while enroot kept failing with the exact error this role exists to prevent. The AppArmor userspace is now installed as the dependency it is on Ubuntu, and a missing apparmor_parser after that stops the play with a message naming pyxis_userns_allow_globally. Turning the global fallback back off left /etc/sysctl.d/60-enroot-userns.conf carrying the lowered default that the scoped profile was meant to replace. The managed setting is now removed when the fallback is off, and the remaining sysctl configuration is reapplied so the value returns to what the distribution ships rather than to one this role invents. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
All three convergence gaps are addressed in 1. Reconcile the effective profile on rerunsThe load was gated on - name: check whether the enroot-nsenter profile is loaded
command: grep -q "^enroot-nsenter " /sys/kernel/security/apparmor/profiles
register: enroot_userns_loaded
changed_when: false
failed_when: false
when: ...
- name: load the enroot-nsenter AppArmor profile
command: apparmor_parser -r /etc/apparmor.d/enroot-nsenter
register: enroot_userns_load
failed_when: false
when:
- ...
- (enroot_userns_profile.changed | default(false))
or (enroot_userns_loaded.rc | default(1) != 0)EvidenceThree consecutive runs, with the kernel profile list stubbed to model each state:
Same three runs against this branch as it stands:
2. apparmor_parser absent is no longer a silent skip- name: install the AppArmor userspace (Ubuntu)
apt:
name: apparmor
state: present
when:
- is_compute
- ansible_distribution == "Ubuntu"
- apparmor_userns_knob.stat.exists | default(false)
- not pyxis_userns_allow_globally
- name: fail when the AppArmor userspace is unavailable
fail:
msg: >-
... the scoped enroot-nsenter profile cannot be loaded and enroot would
fail with "failed to create user namespace: Permission denied". Install
the AppArmor userspace, or set pyxis_userns_allow_globally=true to clear
the restriction for the whole host instead.
when:
- ...
- not apparmor_parser_bin.stat.exists | default(false)The profile is the default path, so its userspace is a dependency of the role rather than something to skip over. If it is still missing after the install, the play stops instead of reporting success while enroot keeps failing. 3. Turning the fallback off takes its setting with it- name: drop the host-wide user namespace sysctl when the fallback is off
ansible.posix.sysctl:
name: kernel.apparmor_restrict_unprivileged_userns
sysctl_file: /etc/sysctl.d/60-enroot-userns.conf
state: absent
register: enroot_userns_sysctl_removed
when:
- is_compute
- apparmor_userns_knob.stat.exists | default(false)
- not pyxis_userns_allow_globally
- name: reapply the remaining sysctl configuration
command: sysctl --system
changed_when: true
when: enroot_userns_sysctl_removed.changed | default(false)Removing the line alone would leave the running kernel at the lowered value until the next boot, so the remaining configuration is reapplied. |
dholt
left a comment
There was a problem hiding this comment.
The parser dependency and unloaded-profile handling address those review points. One migration issue remains in the global-fallback cleanup:
sysctl --system replays unrelated system settings, and it only runs when removal of the managed line reports a change. If restoration fails or is interrupted after that removal, the next run skips it and can leave the runtime restriction disabled.
Please limit restoration to the owned setting and verify the effective value independently of whether the file changed, or fail clearly for administrator reconciliation. Add the interrupted/failed-restoration case; do not reload unrelated host-wide policy.
Problem
Ubuntu 23.10 and later set
kernel.apparmor_restrict_unprivileged_userns=1by default. Under that default,enroot-nsenterfails as soon as pyxis starts a container:The
nvidia.enrootgalaxy role does not handle this, and DeepOps lists Ubuntu 24.04 LTS as a supported OS, so container jobs do not work out of the box on a supported platform.Observed on DGX OS 7.5.0 (Ubuntu-based), Slurm 26.05.1, pyxis 0.11.1.
Fix
Add a sysctl task in
roles/pyxisfor compute nodes. The task is gated on the presence of/proc/sys/kernel/apparmor_restrict_unprivileged_usernsrather than onansible_distribution_version, so it is a no-op on kernels built without AppArmor userns restrictions and does not need updating for future releases.Note on hardening
This relaxes a system-wide hardening default: unprivileged user namespaces are what AppArmor is restricting here. A narrower alternative is an AppArmor profile scoped to
enroot-nsenter(as packaged by some distributions for other userns consumers). If maintainers prefer that approach, I am happy to rework the patch.The sysctl is written to
/etc/sysctl.d/60-enroot-userns.confso it is visible and revertible, rather than being applied only at runtime.